package com.dtflys.easyel.utils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class NumberHelper {

    private final static BigInteger INT_MAX = new BigInteger(Integer.MAX_VALUE + "");
    private final static BigInteger INT_MIN = new BigInteger(Integer.MIN_VALUE + "");
    private final static BigInteger LONG_MAX = new BigInteger(Long.MAX_VALUE + "");
    private final static BigInteger LONG_MIN = new BigInteger(Long.MIN_VALUE + "");


    private final static NumberOperator ADD_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return n1 + n2;
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return n1 + n2;
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return n1 + n2;
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return n1 + n2;
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return n1 + n2;
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return n1 + n2;
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.add(n2);
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.add(n2);
        }
    };


    private final static NumberOperator SUB_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return n1 - n2;
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return n1 - n2;
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return n1 - n2;
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return n1 - n2;
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return n1 - n2;
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return n1 - n2;
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.subtract(n2);
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.subtract(n2);
        }
    };



    private final static NumberOperator MULTI_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return n1 * n2;
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return n1 * n2;
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return n1 * n2;
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return n1 * n2;
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return n1 * n2;
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return n1 * n2;
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.multiply(n2);
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.multiply(n2);
        }
    };


    private final static NumberOperator DIV_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return n1 / n2;
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return n1 / n2;
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return n1 / n2;
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return n1 / n2;
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return n1 / n2;
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return n1 / n2;
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.divide(n2);
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.divide(n2);
        }
    };

    private final static NumberOperator POWER_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return (new BigInteger(n1 + "").pow((int) n2)).intValue();
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return (new BigInteger(n1 + "").pow((int) n2)).intValue();
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return (new BigInteger(n1 + "").pow(n2)).intValue();
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return (new BigInteger(n1 + "").pow(n2.intValue())).longValue();
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return new BigDecimal(n1 + "").pow(n2.intValue()).floatValue();
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return new BigDecimal(n1 + "").pow(n2.intValue()).doubleValue();
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.pow(n2.intValue());
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.pow(n2.intValue());
        }
    };


    private final static NumberOperator MOD_OPERATOR = new NumberOperator() {
        @Override
        public Integer doOperate(Byte n1, Byte n2) {
            return n1 % n2;
        }

        @Override
        public Integer doOperate(Short n1, Short n2) {
            return n1 % n2;
        }

        @Override
        public Integer doOperate(Integer n1, Integer n2) {
            return n1 % n2;
        }

        @Override
        public Long doOperate(Long n1, Long n2) {
            return n1 % n2;
        }

        @Override
        public Float doOperate(Float n1, Float n2) {
            return n1 % n2;
        }

        @Override
        public Double doOperate(Double n1, Double n2) {
            return n1 % n2;
        }

        @Override
        public BigInteger doOperate(BigInteger n1, BigInteger n2) {
            return n1.mod(n2);
        }

        @Override
        public BigDecimal doOperate(BigDecimal n1, BigDecimal n2) {
            return n1.remainder(n2);
        }
    };


    public static Number parseIntegerNumber(String text) {
        BigInteger value = new BigInteger(text);
        if (value.compareTo(INT_MIN) > 0 && value.compareTo(INT_MAX) < 0) {
            return value.intValue();
        }
        if (value.compareTo(LONG_MIN) > 0 && value.compareTo(LONG_MAX) < 0) {
            return value.longValue();
        }
        return value;
    }

    public static boolean isRoundFigure(Number number) {
        if (number instanceof BigDecimal) {
            BigDecimal decimal = (BigDecimal) number;
            return decimal.round(MathContext.UNLIMITED).compareTo(decimal) == 0;
        } else if (number instanceof Double) {
            Double dvalue = (Double) number;
            return dvalue.compareTo(Math.floor(dvalue)) == 0;
        } else if (number instanceof Float) {
            Float fvalue = (Float) number;
            return fvalue.compareTo((float) Math.floor(fvalue)) == 0;
        }
        return true;
    }

    public static Number castNumber(Class targetType, Number number) {
        Class numClass = number.getClass();
        if (targetType == numClass) {
            return number;
        }
        if (targetType == Byte.class) {
            return number.byteValue();
        }
        if (targetType == Short.class) {
            return number.shortValue();
        }
        if (targetType == Integer.class) {
            return number.intValue();
        }
        if (targetType == Long.class) {
            return number.longValue();
        }
        if (targetType == Float.class) {
            return number.floatValue();
        }
        if (targetType == Double.class) {
            return number.doubleValue();
        }
        if (targetType == BigInteger.class) {
            return BigInteger.valueOf(number.longValue());
        }
        if (targetType == BigDecimal.class) {
            return new BigDecimal(number + "");
        }
        throw new ClassCastException(number + " can not be casted to " + targetType.getName());
    }


    public static int compare(Number number1, Number number2) {
        if (number1 == number2) {
            return 0;
        }
        if (number1.equals(number2)) {
            return 0;
        }
        if ((number1 instanceof Byte ||
                number1 instanceof Short ||
                number1 instanceof Integer ||
                number1 instanceof Long) &&
                (number2 instanceof Byte ||
                        number2 instanceof Short ||
                        number2 instanceof Integer ||
                        number2 instanceof Long)) {
            long l1 = number1.longValue();
            long l2 = number2.longValue();
            return l1 > l2 ? 1 : l1 == l2 ? 0 : -1;
        }

        if ((number1 instanceof Float ||
                number1 instanceof Double) &&
                (number2 instanceof Float ||
                        number2 instanceof Double)) {
            double d1 = number1.doubleValue();
            double d2 = number2.doubleValue();
            return d1 > d2 ? 1 : d1 == d2 ? 0 : -1;
        }

        if (number1 instanceof BigInteger && number2 instanceof BigInteger) {
            return ((BigInteger) number1).compareTo((BigInteger) number2);
        }
        if (number1 instanceof BigDecimal && number2 instanceof BigDecimal) {
            return ((BigDecimal) number1).compareTo((BigDecimal) number2);
        }

        BigDecimal bd1 = new BigDecimal(number1.toString());
        BigDecimal bd2 = new BigDecimal(number2.toString());
        return bd1.compareTo(bd2);
    }



    private static Number operate(Number number1, Number number2, NumberOperator operator) {
        if (number1 instanceof Byte && number2 instanceof Byte) {
            return operator.doOperate((Byte) number1, number2.byteValue());
        }
        if (number1 instanceof Short &&
                (number2 instanceof Byte || number2 instanceof Short)) {
            return operator.doOperate((Short) number1, number2.shortValue());
        }
        if (number1 instanceof Integer &&
                (number2 instanceof Byte
                        || number2 instanceof Short
                        || number2 instanceof Integer)) {
            if (operator == POWER_OPERATOR) {
                return operator.doOperate(
                        (BigInteger) castNumber(BigInteger.class, number1),
                        (BigInteger) castNumber(BigInteger.class, number2));
            }
            return operator.doOperate((Integer) number1, number2.intValue());
        }
        if (number1 instanceof Long &&
                (number2 instanceof Byte
                        || number2 instanceof Short
                        || number2 instanceof Integer)) {
            return operator.doOperate(number1.longValue(), number2.longValue());
        }
        if (number1 instanceof BigInteger && number2 instanceof BigInteger) {
            return operator.doOperate((BigInteger) number1, (BigInteger) number2);
        }

        if (number1 instanceof BigDecimal && number2 instanceof BigDecimal) {
            return operator.doOperate((BigDecimal) number1, (BigDecimal) number2);
        }

        return operator.doOperate(new BigDecimal(number1 + ""), new BigDecimal(number2 + ""));
    }

    public static Number add(Number number1, Number number2) {
        return operate(number1, number2, ADD_OPERATOR);
    }


    public static Number sub(Number number1, Number number2) {
        return operate(number1, number2, SUB_OPERATOR);
    }

    public static Number multi(Number number1, Number number2) {
        return operate(number1, number2, MULTI_OPERATOR);
    }

    public static Number div(Number number1, Number number2) {
        return operate(number1, number2, DIV_OPERATOR);
    }

    public static Number power(Number number1, Number number2) {
        return operate(number1, number2, POWER_OPERATOR);
    }

    public static Number mod(Number number1, Number number2) {
        return operate(number1, number2, MOD_OPERATOR);
    }

    private interface NumberOperator {

        Integer doOperate(Byte n1, Byte n2);

        Integer doOperate(Short n1, Short n2);

        Integer doOperate(Integer n1, Integer n2);

        Long doOperate(Long n1, Long n2);

        Float doOperate(Float n1, Float n2);

        Double doOperate(Double n1, Double n2);

        BigInteger doOperate(BigInteger n1, BigInteger n2);

        BigDecimal doOperate(BigDecimal n1, BigDecimal n2);
    }

}
